﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using AIStudio.Wpf.EasyTileManager.Utils;

namespace AIStudio.Wpf.EasyTileManager.Controls
{
    /// <summary>
    /// 按照步骤 1a 或 1b 操作，然后执行步骤 2 以在 XAML 文件中使用此自定义控件。
    ///
    /// 步骤 1a) 在当前项目中存在的 XAML 文件中使用该自定义控件。
    /// 将此 XmlNamespace 特性添加到要使用该特性的标记文件的根 
    /// 元素中: 
    ///
    ///     xmlns:MyNamespace="clr-namespace:AIStudio.Wpf.EasyTileManager.Controls"
    ///
    ///
    /// 步骤 1b) 在其他项目中存在的 XAML 文件中使用该自定义控件。
    /// 将此 XmlNamespace 特性添加到要使用该特性的标记文件的根 
    /// 元素中: 
    ///
    ///     xmlns:MyNamespace="clr-namespace:AIStudio.Wpf.EasyTileManager.Controls;assembly=AIStudio.Wpf.EasyTileManager.Controls"
    ///
    /// 您还需要添加一个从 XAML 文件所在的项目到此项目的项目引用，
    /// 并重新生成以避免编译错误: 
    ///
    ///     在解决方案资源管理器中右击目标项目，然后依次单击
    ///     “添加引用”->“项目”->[浏览查找并选择此项目]
    ///
    ///
    /// 步骤 2)
    /// 继续操作并在 XAML 文件中使用控件。
    ///
    ///     <MyNamespace:TileManagerControl/>
    ///
    /// </summary>
    ///     [TemplatePart(Name = "PART_DragThumb", Type = typeof(DragThumb))]
    //[TemplatePart(Name = "PART_ResizeDecorator", Type = typeof(Control))]
    //[TemplatePart(Name = "PART_ConnectorDecorator", Type = typeof(Control))]
    //[TemplatePart(Name = "PART_ContentPresenter", Type = typeof(ContentPresenter))]
    public class TileManagerControl : Control
    {
        #region Data

        DragAdorner dragAdorner;

        AdornerLayer mAdornerLayer = null;

        Point ptMouseDown;

        Rectangle maskRect = null;

        bool isDrapOverFlag = false;

        #endregion

        #region Constructors

        static TileManagerControl()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(TileManagerControl), new FrameworkPropertyMetadata(typeof(TileManagerControl)));
        }

        public TileManagerControl()
        {

            this.Loaded += new RoutedEventHandler(TileManagerControl_Loaded);
        }

        #endregion

        #region Event Handling Methods

        private void TileManagerControl_Loaded(object sender, RoutedEventArgs e)
        {
            if (base.Template != null)
            {
                TileWidget Tile1 = this.Template.FindName("Tile1", this) as TileWidget;
                TileWidget Tile2 = this.Template.FindName("Tile2", this) as TileWidget;
                TileWidget Tile3 = this.Template.FindName("Tile3", this) as TileWidget;

                Tile1.MaximizedClick += OnMaximizedClick;
                Tile2.MaximizedClick += OnMaximizedClick;
                Tile3.MaximizedClick += OnMaximizedClick;

                Tile1.ExpandClick += OnExpandClick;
                Tile2.ExpandClick += OnExpandClick;
                Tile3.ExpandClick += OnExpandClick;

                Tile1.TileMouseMove += OnPreviewMouseMove;
                Tile2.TileMouseMove += OnPreviewMouseMove;
                Tile3.TileMouseMove += OnPreviewMouseMove;
                Tile1.QueryContinueDrag += OnQueryContinueDrag;
                Tile2.QueryContinueDrag += OnQueryContinueDrag;
                Tile3.QueryContinueDrag += OnQueryContinueDrag;
                Tile1.Drop += OnTileDrop;
                Tile2.Drop += OnTileDrop;
                Tile3.Drop += OnTileDrop;
            }
        }

        private void OnPreviewMouseMove(object sender, MouseEventArgs e)
        {
            if (Mouse.LeftButton != MouseButtonState.Pressed)
                return;

            TileWidget tileView = (TileWidget)sender;

            // Save the location of the cursor when the left mouse button was pressed.
            this.ptMouseDown = MouseUtilities.GetMousePosition(this);
            // Cover the TileView with a Rectangle
            maskRect = ((Grid)tileView.Parent).Children[0] as Rectangle;
            maskRect.Fill = Brushes.White;
            maskRect.AllowDrop = true;
            isDrapOverFlag = false;

            // Create an element which displays the source item while it is dragged.
            dragAdorner = new DragAdorner(tileView, tileView.RenderSize, new VisualBrush(tileView));
            dragAdorner.Opacity = 0.6;
            mAdornerLayer = AdornerLayer.GetAdornerLayer(tileView); // Window class do not have AdornerLayer
            mAdornerLayer.Add(dragAdorner);

            // Perform Drag Operation
            DataObject dataObject = new DataObject(typeof(TileWidget), tileView);
            DragDropEffects allowedEffects = DragDropEffects.Move | DragDropEffects.Copy;
            DragDrop.DoDragDrop(tileView, dataObject, allowedEffects); 

            //Finish Drag Operation
            if (mAdornerLayer != null)
            {
                mAdornerLayer.Remove(dragAdorner);
                dragAdorner = null;
            }

            maskRect.Fill = null;
            maskRect.AllowDrop = false;
            maskRect = null;

            if (!isDrapOverFlag)
            {
                ToDragDrogTile(tileView, tileView);
            }
        }

        private void OnQueryContinueDrag(object sender, QueryContinueDragEventArgs e)
        {
            if (dragAdorner != null)
            {
                Point ptCursor = Utils.MouseUtilities.GetMousePosition(this);

                double left = ptCursor.X - ptMouseDown.X;
                double top = ptCursor.Y - ptMouseDown.Y;

                dragAdorner.SetOffsets(left, top);
            }
        }

        private void OnTileDrop(object sender, DragEventArgs e)
        {
            IDataObject data = e.Data;

            if (data.GetDataPresent(typeof(TileWidget)))
            {
                TileWidget source = data.GetData(typeof(TileWidget)) as TileWidget;
                TileWidget target = (TileWidget)sender;

                ToDragDrogTile(source, target);

                if (source != target)
                {
                    ToMoveLocationOfTile(target, source);

                    if (target.CurrentTileState == TileState.Maximized)
                    {
                        target.CurrentTileState = source.CurrentTileState;

                        source.CurrentTileState = TileState.Maximized;
                    }
                    else
                    {
                        if (source.CurrentTileState == TileState.Maximized)
                        {
                            source.CurrentTileState = target.CurrentTileState;

                            target.CurrentTileState = TileState.Maximized;
                        }
                    }
                }

                isDrapOverFlag = true;
            }
        }

        private void OnMaximizedClick(object sender, RoutedEventArgs e)
        {
            TileWidget tile = (TileWidget)sender;

            if (tile.CurrentTileState == TileState.Maximized)
            {
                return;
            }

            if (this.Template != null)
            {
                Grid PrimaryGrid = this.Template.FindName("PrimaryGrid", this) as Grid;

                if (PrimaryGrid != null)
                {
                    TileWidget target = PrimaryGrid.Children[1] as TileWidget;

                    TileSwapping(tile, target);

                    target.CurrentTileState = tile.CurrentTileState;
                    tile.CurrentTileState = TileState.Maximized;
                }
            }
        }

        private void OnExpandClick(object sender, RoutedEventArgs e)
        {
            TileWidget tileView = sender as TileWidget;

            if (this.Template == null)
            {
                return;
            }

            Grid SeconddaryGrid = this.Template.FindName("SeconddaryGrid", this) as Grid;

            if (SeconddaryGrid == null)
            {
                return;
            }

            if (tileView.CurrentTileState == TileState.Normal)
            {
                tileView.CurrentTileState = TileState.MinimizedExpanded;
                SeconddaryGrid.RowDefinitions[Grid.GetRow((Grid)tileView.Parent)].Height = new GridLength(22, GridUnitType.Pixel); // tileView.Height

                if (!SeconddaryGrid.RowDefinitions[0].Height.IsStar && !SeconddaryGrid.RowDefinitions[1].Height.IsStar)
                {
                    SeconddaryGrid.RowDefinitions[2].Height = new GridLength(1, GridUnitType.Star);
                }
            }
            else if (tileView.CurrentTileState == TileState.MinimizedExpanded)
            {
                tileView.CurrentTileState = TileState.Normal;
                SeconddaryGrid.RowDefinitions[Grid.GetRow((Grid)tileView.Parent)].Height = new GridLength(1, GridUnitType.Star);

                if (SeconddaryGrid.RowDefinitions[2].Height.IsStar)
                {
                    SeconddaryGrid.RowDefinitions[2].Height = new GridLength(0.1, GridUnitType.Pixel);
                }
            }
        }

        #endregion

        #region  Private Helpers

        private void TileSwapping(TileWidget source, TileWidget target)
        {
            ToMoveLocationOfTile(source, target);

            ToMoveLocationOfTile(target, source);
        }

        private void ToMoveLocationOfTile(TileWidget source, TileWidget target)
        {
            if (this.Template == null)
            {
                return;
            }

            Grid Layout = this.Template.FindName("Layout", this) as Grid;

            if (Layout == null)
            {
                return;
            }

            source.Tag = target.Parent;

            Storyboard storyboard = new Storyboard();
            storyboard.Completed -= OnToMoveStoryboardCompleted;
            storyboard.FillBehavior = FillBehavior.Stop;
            storyboard.Completed += OnToMoveStoryboardCompleted;

            Int32AnimationUsingKeyFrames animation1 = new Int32AnimationUsingKeyFrames();
            animation1.KeyFrames.Add(new EasingInt32KeyFrame() { Value = 100, KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(0.1)) });
            Storyboard.SetTarget(animation1, source);
            Storyboard.SetTargetProperty(animation1, new PropertyPath("(Panel.ZIndex)"));
            storyboard.Children.Add(animation1);

            double ks = source.ActualWidth / source.ActualHeight;
            double kt = target.ActualWidth / target.ActualHeight;
            double tw = 0, th = 0;
            if (ks < kt)
            {
                if (source.ActualWidth < target.ActualWidth)
                {
                    tw = source.ActualWidth;
                }
                else
                {
                    tw = target.ActualWidth;
                }
                th = target.ActualHeight / target.ActualWidth * tw;
            }
            else
            {
                if (source.ActualWidth > target.ActualWidth)
                {
                    if (source.ActualHeight > target.ActualHeight)
                    {
                        th = target.ActualHeight;
                    }
                    else
                    {
                        th = source.ActualHeight;
                    }
                }
                else
                {
                    th = source.ActualHeight;
                }
                tw = target.ActualWidth / target.ActualHeight * th;
            }
            DoubleAnimationUsingKeyFrames animation2 = new DoubleAnimationUsingKeyFrames();
            animation2.KeyFrames.Add(new EasingDoubleKeyFrame() { KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(0)), Value = th });
            Storyboard.SetTarget(animation2, source);
            Storyboard.SetTargetProperty(animation2, new PropertyPath("(FrameworkElement.Height)"));
            storyboard.Children.Add(animation2);

            DoubleAnimationUsingKeyFrames animation3 = new DoubleAnimationUsingKeyFrames();
            animation3.KeyFrames.Add(new EasingDoubleKeyFrame() { KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(0)), Value = tw });
            Storyboard.SetTarget(animation3, source);
            Storyboard.SetTargetProperty(animation3, new PropertyPath("(FrameworkElement.Width)"));
            storyboard.Children.Add(animation3);

            ObjectAnimationUsingKeyFrames animation4 = new ObjectAnimationUsingKeyFrames();
            animation4.KeyFrames.Add(new DiscreteObjectKeyFrame() { KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(0)), Value = VerticalAlignment.Top });
            Storyboard.SetTarget(animation4, source);
            Storyboard.SetTargetProperty(animation4, new PropertyPath("(FrameworkElement.VerticalAlignment)"));
            storyboard.Children.Add(animation4);

            ObjectAnimationUsingKeyFrames animation5 = new ObjectAnimationUsingKeyFrames();
            animation5.KeyFrames.Add(new DiscreteObjectKeyFrame() { KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(0)), Value = HorizontalAlignment.Left });
            Storyboard.SetTarget(animation5, source);
            Storyboard.SetTargetProperty(animation5, new PropertyPath("(FrameworkElement.HorizontalAlignment)"));
            storyboard.Children.Add(animation5);

            Vector space = target.TranslatePoint(new Point(0, 0), Layout) - source.TranslatePoint(new Point(0, 0), Layout);
            double left = space.X;
            double top = space.Y;
            space = source.TranslatePoint(new Point(source.ActualWidth, source.ActualHeight), Layout) - target.TranslatePoint(new Point(target.ActualWidth, target.ActualHeight), Layout);
            double right = space.X;
            double bottom = space.Y;
            ThicknessAnimation animation6 = new ThicknessAnimation();
            animation6.To = new Thickness(left, top, right, bottom);
            animation6.BeginTime = TimeSpan.FromSeconds(0.1);
            animation6.Duration = TimeSpan.FromSeconds(0.3);
            animation6.EasingFunction = new CircleEase { EasingMode = EasingMode.EaseOut };
            Storyboard.SetTarget(animation6, source);
            Storyboard.SetTargetProperty(animation6, new PropertyPath("(FrameworkElement.Margin)"));
            storyboard.Children.Add(animation6);

            DoubleAnimation animation7 = new DoubleAnimation();
            animation7.To = target.ActualWidth;
            animation7.BeginTime = TimeSpan.FromSeconds(0.1);
            animation7.Duration = TimeSpan.FromSeconds(0.3);
            animation7.EasingFunction = new CircleEase { EasingMode = EasingMode.EaseOut };
            Storyboard.SetTarget(animation7, source);
            Storyboard.SetTargetProperty(animation7, new PropertyPath("(FrameworkElement.Width)"));
            storyboard.Children.Add(animation7);

            DoubleAnimation animation8 = new DoubleAnimation();
            animation8.To = target.ActualHeight;
            animation8.BeginTime = TimeSpan.FromSeconds(0.1);
            animation8.Duration = TimeSpan.FromSeconds(0.3);
            animation8.EasingFunction = new CircleEase { EasingMode = EasingMode.EaseOut };
            Storyboard.SetTarget(animation8, source);
            Storyboard.SetTargetProperty(animation8, new PropertyPath("(FrameworkElement.Height)"));
            storyboard.Children.Add(animation8);

            storyboard.Begin();
        }

        private void OnToMoveStoryboardCompleted(object sender, EventArgs e)
        {
            if ((sender as ClockGroup).Children.Count > 0)
            {
                Timeline timeline = ((sender as ClockGroup).Children[0]).Timeline;

                TileWidget uiElement = Storyboard.GetTarget(timeline) as TileWidget;

                if (uiElement.Tag is Grid)
                {
                    ((Grid)uiElement.Parent).Children.Remove(uiElement);
                    ((Grid)uiElement.Tag).Children.Add(uiElement);

                    uiElement.HorizontalAlignment = HorizontalAlignment.Stretch;
                    uiElement.VerticalAlignment = VerticalAlignment.Stretch;

                    uiElement.Margin = new Thickness(0);
                    Panel.SetZIndex(uiElement, 0);
                }
            }
        }

        private void ToDragDrogTile(TileWidget source, TileWidget target)
        {
            Grid Layout = this.Template.FindName("Layout", this) as Grid;

            if (Layout == null)
            {
                return;
            }

            source.Tag = target.Parent;

            Storyboard storyboard = new Storyboard();
            storyboard.Completed -= OnToDragDrogStoryboardCompleted;
            storyboard.FillBehavior = FillBehavior.Stop;
            storyboard.Completed += OnToDragDrogStoryboardCompleted;

            Int32AnimationUsingKeyFrames animation1 = new Int32AnimationUsingKeyFrames();
            animation1.KeyFrames.Add(new EasingInt32KeyFrame() { Value = 1000, KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(0.1)) });
            Storyboard.SetTarget(animation1, source);
            Storyboard.SetTargetProperty(animation1, new PropertyPath("(Panel.ZIndex)"));
            storyboard.Children.Add(animation1);

            Vector space = target.TranslatePoint(new Point(target.ActualWidth, target.ActualHeight), Layout) - source.TranslatePoint(new Point(0, 0), Layout);
            double tLeft = space.X;
            double tTop = space.Y;
            space = target.TranslatePoint(new Point(0, 0), Layout) - source.TranslatePoint(new Point(0, 0), Layout);
            double left = space.X;
            double top = space.Y;
            space = source.TranslatePoint(new Point(source.ActualWidth, source.ActualHeight), Layout) - target.TranslatePoint(new Point(target.ActualWidth, target.ActualHeight), Layout);
            double right = space.X;
            double bottom = space.Y;
            ThicknessAnimation animation2 = new ThicknessAnimation();
            animation2.From = new Thickness(tLeft, tTop, right, bottom);
            animation2.To = new Thickness(left, top, right, bottom);
            animation2.BeginTime = TimeSpan.FromSeconds(0);
            animation2.Duration = TimeSpan.FromSeconds(0.4);
            animation2.EasingFunction = new CircleEase { EasingMode = EasingMode.EaseOut };
            Storyboard.SetTarget(animation2, source);
            Storyboard.SetTargetProperty(animation2, new PropertyPath("(FrameworkElement.Margin)"));
            storyboard.Children.Add(animation2);

            storyboard.Begin();
        }

        private void OnToDragDrogStoryboardCompleted(object sender, EventArgs e)
        {
            if ((sender as ClockGroup).Children.Count > 0)
            {
                Timeline timeline = ((sender as ClockGroup).Children[0]).Timeline;

                TileWidget uiElement = Storyboard.GetTarget(timeline) as TileWidget;

                if (uiElement.Tag is Grid)
                {
                    ((Grid)uiElement.Parent).Children.Remove(uiElement);
                    ((Grid)uiElement.Tag).Children.Add(uiElement);

                    uiElement.HorizontalAlignment = HorizontalAlignment.Stretch;
                    uiElement.VerticalAlignment = VerticalAlignment.Stretch;

                    uiElement.Margin = new Thickness(0);
                    Panel.SetZIndex(uiElement, 0);
                }
            }
        }


        #endregion

        #region Dependency Property

        public object WidgetContent1
        {
            get { return (object)GetValue(WidgetContent1Property); }
            set { SetValue(WidgetContent1Property, value); }
        }

        // Using a DependencyProperty as the backing store for WidgetContent1.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty WidgetContent1Property =
            DependencyProperty.Register("WidgetContent1", typeof(object), typeof(TileManagerControl), new PropertyMetadata(null));

        static void OnWidgetContent1Changed(DependencyObject property, DependencyPropertyChangedEventArgs args)
        {
            TileManagerControl control = property as TileManagerControl;

            TileWidget Tile1 = control.Template.FindName("Tile1", control) as TileWidget;
            if (Tile1 != null)
            {
                Tile1.Content = args.NewValue;
            }

        }

        public string Title1
        {
            get { return (string)GetValue(Title1Property); }
            set { SetValue(Title1Property, value); }
        }

        // Using a DependencyProperty as the backing store for Title1.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty Title1Property =
            DependencyProperty.Register("Title1", typeof(string), typeof(TileManagerControl), new PropertyMetadata(string.Empty));

        static void OnTitle1Changed(DependencyObject property, DependencyPropertyChangedEventArgs args)
        {
            string value = string.Empty;

            value = args.NewValue.ToString();

            TileManagerControl control = property as TileManagerControl;

            TileWidget Tile1 = control.Template.FindName("Tile1", control) as TileWidget;
            if (Tile1 != null)
            {
                Tile1.Title = value;
            }

        }

        public object WidgetContent2
        {
            get { return (object)GetValue(WidgetContent2Property); }
            set { SetValue(WidgetContent2Property, value); }
        }

        // Using a DependencyProperty as the backing store for WidgetContent1.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty WidgetContent2Property =
            DependencyProperty.Register("WidgetContent2", typeof(object), typeof(TileManagerControl), new PropertyMetadata(null));

        static void OnWidgetContent2Changed(DependencyObject property, DependencyPropertyChangedEventArgs args)
        {
            TileManagerControl control = property as TileManagerControl;

            TileWidget Tile2 = control.Template.FindName("Tile2", control) as TileWidget;
            if (Tile2 != null)
            {
                Tile2.Content = args.NewValue;
            }
        }

        public string Title2
        {
            get { return (string)GetValue(Title2Property); }
            set { SetValue(Title2Property, value); }
        }

        // Using a DependencyProperty as the backing store for Title1.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty Title2Property =
            DependencyProperty.Register("Title2", typeof(string), typeof(TileManagerControl), new PropertyMetadata(string.Empty));

        static void OnTitle2Changed(DependencyObject property, DependencyPropertyChangedEventArgs args)
        {
            string value = string.Empty;

            value = args.NewValue.ToString();

            TileManagerControl control = property as TileManagerControl;

            TileWidget Tile2 = control.Template.FindName("Tile2", control) as TileWidget;
            if (Tile2 != null)
            {
                Tile2.Title = value;
            }
        }

        public object WidgetContent3
        {
            get { return (object)GetValue(WidgetContent3Property); }
            set { SetValue(WidgetContent3Property, value); }
        }

        // Using a DependencyProperty as the backing store for WidgetContent1.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty WidgetContent3Property =
            DependencyProperty.Register("WidgetContent3", typeof(object), typeof(TileManagerControl), new PropertyMetadata(null));

        static void OnWidgetContent3Changed(DependencyObject property, DependencyPropertyChangedEventArgs args)
        {
            TileManagerControl control = property as TileManagerControl;

            TileWidget Tile3 = control.Template.FindName("Tile3", control) as TileWidget;
            if (Tile3 != null)
            {
                Tile3.Content = args.NewValue;
            }
        }

        public string Title3
        {
            get { return (string)GetValue(Title3Property); }
            set { SetValue(Title3Property, value); }
        }

        // Using a DependencyProperty as the backing store for Title1.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty Title3Property =
            DependencyProperty.Register("Title3", typeof(string), typeof(TileManagerControl), new PropertyMetadata(string.Empty));

        static void OnTitle3Changed(DependencyObject property, DependencyPropertyChangedEventArgs args)
        {
            string value = string.Empty;

            value = args.NewValue.ToString();

            TileManagerControl control = property as TileManagerControl;

            TileWidget Tile3 = control.Template.FindName("Tile3", control) as TileWidget;
            if (Tile3 != null)
            {
                Tile3.Title = value;
            }
        }

        #endregion
    }
}
